{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 基于 Attention 的 seq2seq 模型实现"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0.0\n",
      "sys.version_info(major=3, minor=6, micro=10, releaselevel='final', serial=0)\n",
      "matplotlib 3.1.2\n",
      "numpy 1.18.1\n",
      "pandas 0.25.3\n",
      "sklearn 0.22.1\n",
      "tensorflow 2.0.0\n",
      "tensorflow_core.keras 2.2.4-tf\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "import tensorflow as tf\n",
    "from tensorflow import keras\n",
    "\n",
    "print(tf.__version__)\n",
    "print(sys.version_info)\n",
    "for module in mpl,np,pd,sklearn,tf,keras:\n",
    "    print(module.__name__,module.__version__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# gpus = tf.config.experimental.list_physical_devices('GPU')\n",
    "# for gpu in gpus:\n",
    "#     tf.config.experimental.set_memory_growth(gpu, True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "步骤：\n",
    "1. preprocessing data\n",
    "2. build model\n",
    "   1. encoder\n",
    "   2. attention\n",
    "   3. decoder\n",
    "   4. loss & optimizer\n",
    "   5. train\n",
    "3. evaluation\n",
    "   1. given sentence, return translated results\n",
    "   2. visualize results (attention)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据处理\n",
    "### 数据预处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Then what?\n",
      "¿Entonces que?\n"
     ]
    }
   ],
   "source": [
    "en_spa_file_path = './data/data_spa_en/spa.txt'\n",
    "\n",
    "# 将unicod编码转化为ascii，如果有多个ascii组成，则拆分，去掉重音\n",
    "import unicodedata\n",
    "def unicode_to_ascii(s):\n",
    "    return ''.join(c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn')\n",
    "\n",
    "# test\n",
    "en_sentence = 'Then what?'\n",
    "sp_sentence = '¿Entonces qué?'\n",
    "\n",
    "print(unicode_to_ascii(en_sentence))\n",
    "print(unicode_to_ascii(sp_sentence))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<start> then what ? <end>\n",
      "<start> ¿ entonces que ? <end>\n"
     ]
    }
   ],
   "source": [
    "# 西方语言常用：标点符号和字母分开\n",
    "import re\n",
    "def preprocess_sentence(s):\n",
    "    # 转化成ascii，变小写去空格\n",
    "    s = unicode_to_ascii(s.lower().strip())\n",
    "    \n",
    "    # 标点符号前后加空格\n",
    "    s = re.sub(r'([?.!,¿])', r' \\1 ', s)\n",
    "    # 多余的空格变成一个空格\n",
    "    s = re.sub(r'[\" \"]+', ' ', s)\n",
    "    # 除了标点符号和字母外都是空格\n",
    "    s = re.sub(r'[^a-zA-Z?.!,¿]', ' ', s)\n",
    "    # 去掉前后空格\n",
    "    s = s.rstrip().strip()\n",
    "    # 前后加标记\n",
    "    s = '<start> ' + s + ' <end>'\n",
    "    return s\n",
    "\n",
    "# test\n",
    "print(preprocess_sentence(en_sentence))\n",
    "print(preprocess_sentence(sp_sentence))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 3, 5) (2, 4, 6)\n"
     ]
    }
   ],
   "source": [
    "# 解包和zip联用举例\n",
    "a = [(1, 2), (3, 4), (5, 6)]\n",
    "c, d = zip(*a)\n",
    "print(c, d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<start> it may be impossible to get a completely error free corpus due to the nature of this kind of collaborative effort . however , if we encourage members to contribute sentences in their own languages rather than experiment in languages they are learning , we might be able to minimize errors . <end>\n",
      "<start> puede que sea imposible obtener un corpus completamente libre de errores debido a la naturaleza de este tipo de esfuerzo de colaboracion . sin embargo , si animamos a los miembros a contribuir frases en sus propios idiomas en lugar de experimentar con los idiomas que estan aprendiendo , podriamos ser capaces de minimizar los errores . <end>\n"
     ]
    }
   ],
   "source": [
    "# 解析文件\n",
    "def parse_data(filename):\n",
    "    lines = open(filename, encoding='utf-8').read().strip().split('\\n')\n",
    "    sentence_pairs = [line.split('\\t') for line in lines]\n",
    "    preprocess_sentence_pairs = [\n",
    "        (preprocess_sentence(en), preprocess_sentence(sp)) for en, sp in sentence_pairs]\n",
    "    # 解包和zip联用：将每一个元组解开，重新组合成两个新的列表\n",
    "    return zip(*preprocess_sentence_pairs)\n",
    "\n",
    "en_dataset, sp_dataset = parse_data(en_spa_file_path)\n",
    "print(en_dataset[-1])\n",
    "print(sp_dataset[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 数据id化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "16 11\n"
     ]
    }
   ],
   "source": [
    "def tokenizer(lang):\n",
    "    lang_tokenizer = keras.preprocessing.text.Tokenizer(num_words=None, filters='', split=' ')\n",
    "    # 统计词频，生成词表\n",
    "    lang_tokenizer.fit_on_texts(lang)\n",
    "    # id化\n",
    "    tensor = lang_tokenizer.texts_to_sequences(lang)\n",
    "    # padding\n",
    "    tensor = keras.preprocessing.sequence.pad_sequences(tensor, padding='post')\n",
    "    return tensor, lang_tokenizer\n",
    "\n",
    "input_tensor, input_tokenizer = tokenizer(sp_dataset[0:30000])\n",
    "output_tensor, output_tokenizer = tokenizer(en_dataset[0:30000])\n",
    "\n",
    "def max_length(tensor):\n",
    "    return max(len(t) for t in tensor)\n",
    "\n",
    "max_length_input = max_length(input_tensor)\n",
    "max_length_output = max_length(output_tensor)\n",
    "print(max_length_input, max_length_output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(24000, 6000, 24000, 6000)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 训练集和验证集切分\n",
    "from sklearn.model_selection import train_test_split\n",
    "input_train, input_eval, output_train, output_eval = train_test_split(\n",
    "    input_tensor, output_tensor, test_size = 0.2)\n",
    "\n",
    "len(input_train), len(input_eval), len(output_train), len(output_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 --> <start>\n",
      "80 --> voy\n",
      "10 --> a\n",
      "9235 --> regar\n",
      "9 --> el\n",
      "1807 --> jardin\n",
      "3 --> .\n",
      "2 --> <end>\n",
      "\n",
      "1 --> <start>\n",
      "4 --> i\n",
      "38 --> ll\n",
      "269 --> water\n",
      "13 --> the\n",
      "1441 --> garden\n",
      "3 --> .\n",
      "2 --> <end>\n"
     ]
    }
   ],
   "source": [
    "# 验证tokenizer是否转化正确\n",
    "def convert(example, tokenizer):\n",
    "    for t in example:\n",
    "        if t != 0:\n",
    "            print('%d --> %s' % (t, tokenizer.index_word[t]))\n",
    "            \n",
    "convert(input_train[0], input_tokenizer)\n",
    "print()\n",
    "convert(output_train[0], output_tokenizer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 生成Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_dataset(input_tensor, output_tensor, batch_size, epochs, shuffle):\n",
    "    dataset = tf.data.Dataset.from_tensor_slices((input_tensor, output_tensor))\n",
    "    if shuffle:\n",
    "        dataset = dataset.shuffle(30000)\n",
    "    dataset = dataset.repeat(epochs).batch(batch_size, drop_remainder = True)\n",
    "    return dataset\n",
    "\n",
    "batch_size = 64\n",
    "epochs = 20\n",
    "\n",
    "train_dataset = make_dataset(input_train, output_train, batch_size, epochs, True)\n",
    "eval_dataset = make_dataset(input_eval, output_eval, batch_size, 1, False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(64, 16)\n",
      "(64, 11)\n",
      "tf.Tensor(\n",
      "[[   1   17   12 ...    0    0    0]\n",
      " [   1   25  174 ...    0    0    0]\n",
      " [   1    5 6675 ...    0    0    0]\n",
      " ...\n",
      " [   1   24 1190 ...    0    0    0]\n",
      " [   1   80   10 ...    0    0    0]\n",
      " [   1    9   44 ...    0    0    0]], shape=(64, 16), dtype=int32)\n",
      "tf.Tensor(\n",
      "[[   1    4   29  106   21  643    3    2    0    0    0]\n",
      " [   1    4  129  408    3    2    0    0    0    0    0]\n",
      " [   1   40    5  249   10   83    6    2    0    0    0]\n",
      " [   1    4   16   34  674    3    2    0    0    0    0]\n",
      " [   1   92    4  178   10   23    6    2    0    0    0]\n",
      " [   1    4  438    9   70   15    5    3    2    0    0]\n",
      " [   1    7    8 1633 1855    3    2    0    0    0    0]\n",
      " [   1   13  480    8  818    3    2    0    0    0    0]\n",
      " [   1  459   13 2141    3    2    0    0    0    0    0]\n",
      " [   1    4 1327    5    3    2    0    0    0    0    0]\n",
      " [   1   29    5  355   20    6    2    0    0    0    0]\n",
      " [   1    7   26 1335    3    2    0    0    0    0    0]\n",
      " [   1    4   16 2058    3    2    0    0    0    0    0]\n",
      " [   1    4   69   34  445    3    2    0    0    0    0]\n",
      " [   1   14   23  253  112    3    2    0    0    0    0]\n",
      " [   1  117   36   13   83    3    2    0    0    0    0]\n",
      " [   1   17   24  425    3    2    0    0    0    0    0]\n",
      " [   1    5   38  191  452    3    2    0    0    0    0]\n",
      " [   1   33   90   15  239    3    2    0    0    0    0]\n",
      " [   1    4  589   35  687    3    2    0    0    0    0]\n",
      " [   1   17  542   74    3    2    0    0    0    0    0]\n",
      " [   1    9   92  514    3    2    0    0    0    0    0]\n",
      " [   1    7  343   49  306    3    2    0    0    0    0]\n",
      " [   1    7   40   72   19    3    2    0    0    0    0]\n",
      " [   1    7    8   10   67  932    3    2    0    0    0]\n",
      " [   1    7  298  186    3    2    0    0    0    0    0]\n",
      " [   1   20  523    8  955    3    2    0    0    0    0]\n",
      " [   1    4   73 1305  278    3    2    0    0    0    0]\n",
      " [   1    7 2249   82    3    2    0    0    0    0    0]\n",
      " [   1   14    8   10 1091    3    2    0    0    0    0]\n",
      " [   1 1230   13  730    3    2    0    0    0    0    0]\n",
      " [   1  580   50   52   39    3    2    0    0    0    0]\n",
      " [   1  428   15 1560   10  346    6    2    0    0    0]\n",
      " [   1   58  323   50   21  190  145    3    2    0    0]\n",
      " [   1    4   16   36 1037   63   19    3    2    0    0]\n",
      " [   1    5   29 1110    3    2    0    0    0    0    0]\n",
      " [   1   17   24  250  316    3    2    0    0    0    0]\n",
      " [   1    4   45   15   33    3    2    0    0    0    0]\n",
      " [   1    4   26  184    3    2    0    0    0    0    0]\n",
      " [   1  161   98    8 2650    3    2    0    0    0    0]\n",
      " [   1    8   20   84  162    6    2    0    0    0    0]\n",
      " [   1   25    5  187   80   18    6    2    0    0    0]\n",
      " [   1   68   11   30  248    6    2    0    0    0    0]\n",
      " [   1  509    7    3    2    0    0    0    0    0    0]\n",
      " [   1   23    5  290  231    6    2    0    0    0    0]\n",
      " [   1   28 3363    3    2    0    0    0    0    0    0]\n",
      " [   1   21  827    8   36 1298    3    2    0    0    0]\n",
      " [   1    4   57   10 1219    3    2    0    0    0    0]\n",
      " [   1   20   26   21  451    3    2    0    0    0    0]\n",
      " [   1   56    8   20  461    6    2    0    0    0    0]\n",
      " [   1    7    8   10 2244    3    2    0    0    0    0]\n",
      " [   1   25    5   36   10  276    6    2    0    0    0]\n",
      " [   1   31    8 1539    3    2    0    0    0    0    0]\n",
      " [   1    4   69   36   13  168    3    2    0    0    0]\n",
      " [   1    4   35   15   29  206    3    2    0    0    0]\n",
      " [   1   17   62  302    9    3    2    0    0    0    0]\n",
      " [   1    4   16    7    3    2    0    0    0    0    0]\n",
      " [   1    4  163  223  288    3    2    0    0    0    0]\n",
      " [   1    4  283    4   91  609    3    2    0    0    0]\n",
      " [   1    4   16  565    3    2    0    0    0    0    0]\n",
      " [   1    4  341   10  410  149    3    2    0    0    0]\n",
      " [   1    4   16  627    3    2    0    0    0    0    0]\n",
      " [   1    4   16  121   15  279    3    2    0    0    0]\n",
      " [   1   14   59   10  643    3    2    0    0    0    0]], shape=(64, 11), dtype=int32)\n"
     ]
    }
   ],
   "source": [
    "for x, y in train_dataset.take(1):\n",
    "    print(x.shape)\n",
    "    print(y.shape)\n",
    "    print(x)\n",
    "    print(y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义超参数\n",
    "embedding_units = 256\n",
    "units = 1024\n",
    "input_vocab_size = len(input_tokenizer.word_index) + 1\n",
    "output_vocab_size = len(output_tokenizer.word_index) + 1\n",
    "batch_size = 64"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Encoder构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Encoder(keras.Model):\n",
    "    def __init__(self, vocab_size, embedding_units, encoding_units, batch_size):\n",
    "        super(Encoder, self).__init__()\n",
    "        self.batch_size = batch_size\n",
    "        self.encoding_units = encoding_units\n",
    "        self.embedding = keras.layers.Embedding(vocab_size, embedding_units)\n",
    "        self.gru = keras.layers.GRU(self.encoding_units, return_sequences=True,\n",
    "                                    return_state=True, recurrent_initializer='glorot_uniform')\n",
    "        \n",
    "    def call(self, x, hidden):\n",
    "        x = self.embedding(x)\n",
    "        output, state = self.gru(x, initial_state = hidden)\n",
    "        return output, state\n",
    "    \n",
    "    def initialize_hidden_state(self):\n",
    "        return tf.zeros((self.batch_size, self.encoding_units))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(64, 16, 1024)\n",
      "(64, 1024)\n"
     ]
    }
   ],
   "source": [
    "# test\n",
    "encoder = Encoder(input_vocab_size, embedding_units, units, batch_size)\n",
    "sample_hidden = encoder.initialize_hidden_state()\n",
    "sample_output, sample_hidden = encoder(x, sample_hidden)\n",
    "print(sample_output.shape)\n",
    "print(sample_hidden.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Attention 构建 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BahdanauAttention(keras.Model):\n",
    "    def __init__(self, units):\n",
    "        super(BahdanauAttention, self).__init__()\n",
    "        self.W1 = keras.layers.Dense(units)\n",
    "        self.W2 = keras.layers.Dense(units)\n",
    "        self.V = keras.layers.Dense(1)\n",
    "        \n",
    "    def call(self, decoder_hidden, encoder_outputs):\n",
    "        # decoder_hidden.shape: (batch_size, units)\n",
    "        # encoder_outputs.shape: (batch_size, length, units)\n",
    "        # 维度不同，需要扩维\n",
    "        decoder_hidden_with_time_axis = tf.expand_dims(decoder_hidden, 1)\n",
    "        \n",
    "        # before V: (batch_size, length, units)\n",
    "        # after V: (batch_size, length, 1)\n",
    "        # 计算score\n",
    "        score = self.V(tf.nn.tanh(\n",
    "            self.W1(encoder_outputs) + self.W2(decoder_hidden_with_time_axis)))\n",
    "        \n",
    "        # attention_weights.shape: (batch_size, length, 1)\n",
    "        # 得到权重\n",
    "        attention_weights = tf.nn.softmax(score, axis=1)\n",
    "        \n",
    "        # context_vector.shape: (batch_size, length, 1)\n",
    "        # 加权求和\n",
    "        context_vector = attention_weights * encoder_outputs\n",
    "        # context_vector.shape: (batch_size, units)\n",
    "        context_vector = tf.reduce_sum(context_vector, axis=1)\n",
    "        \n",
    "        return context_vector, attention_weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(64, 1024)\n",
      "(64, 16, 1)\n"
     ]
    }
   ],
   "source": [
    "# test\n",
    "attention_model = BahdanauAttention(units=10)\n",
    "attention_results, attention_weights = attention_model(sample_hidden, sample_output)\n",
    "print(attention_results.shape)\n",
    "print(attention_weights.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Decoder构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Decoder(keras.Model):\n",
    "    def __init__(self, vocab_size, embedding_units, decoding_units, batch_size):\n",
    "        super(Decoder, self).__init__()\n",
    "        self.batch_size = batch_size\n",
    "        self.decoding_units = decoding_units\n",
    "        self.embedding = keras.layers.Embedding(vocab_size, embedding_units)\n",
    "        self.gru = keras.layers.GRU(self.decoding_units, return_sequences=True,\n",
    "                                    return_state=True, recurrent_initializer='glorot_uniform')\n",
    "        self.fc = keras.layers.Dense(vocab_size)\n",
    "        self.attention = BahdanauAttention(self.decoding_units)\n",
    "        \n",
    "    def call(self, x, hidden, encoding_outputs):\n",
    "        # context_vector.shape: (batch_size, units)\n",
    "        # 计算attention权重\n",
    "        context_vector, attention_weights = self.attention(hidden, encoding_outputs)\n",
    "        \n",
    "        # before embedding: x.shape: (batch_size, 1)\n",
    "        # after embedding: x.shape: (batch_size, 1, embedding_units)\n",
    "        x = self.embedding(x)\n",
    "        \n",
    "        # 先将attention 权重扩维，再与x连起来\n",
    "        combined_x = tf.concat([tf.expand_dims(context_vector, 1), x], axis = -1)\n",
    "        \n",
    "        # output.shape: (batch_size, 1, decoding_units)\n",
    "        # state.shape: (batch_size, decoding_units)\n",
    "        output, state = self.gru(combined_x)\n",
    "        \n",
    "        # output.shape: (batch_size, decoding_units)\n",
    "        output = tf.reshape(output, (-1, output.shape[2]))\n",
    "        \n",
    "        # output.shape: (batch_size, vocab_size)\n",
    "        output = self.fc(output)\n",
    "        \n",
    "        return output, state, attention_weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(64, 4834)\n",
      "(64, 1024)\n",
      "(64, 16, 1)\n"
     ]
    }
   ],
   "source": [
    "# test\n",
    "decoder = Decoder(output_vocab_size, embedding_units, units, batch_size)\n",
    "\n",
    "outputs = decoder(tf.random.uniform((batch_size, 1)), sample_hidden, sample_output)\n",
    "decoder_output, decoder_hidden, decoder_aw = outputs\n",
    "\n",
    "print(decoder_output.shape)\n",
    "print(decoder_hidden.shape)\n",
    "print(decoder_aw.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 损失函数和单步训练函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = keras.optimizers.Adam()\n",
    "loss_object = keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')\n",
    "\n",
    "# 计算单步损失函数\n",
    "def loss_function(real, pred):\n",
    "    # 计算所有非padding部分\n",
    "    mask = tf.math.logical_not(tf.math.equal(real, 0))\n",
    "    loss_ = loss_object(real, pred)\n",
    "    \n",
    "    # mask类型转化，与loss_保持一致\n",
    "    mask = tf.cast(mask, dtype=loss_.dtype)\n",
    "    loss_ *= mask\n",
    "    \n",
    "    return tf.reduce_mean(loss_)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义单步训练函数\n",
    "@tf.function\n",
    "def train_step(inp, targ, encoding_hidden):\n",
    "    loss = 0\n",
    "    with tf.GradientTape() as tape:\n",
    "        encoding_outputs, encoding_hidden = encoder(inp, encoding_hidden)\n",
    "        decoding_hidden = encoding_hidden\n",
    "        \n",
    "        # eg:对于输入长度为5的序列，输出需要4步\n",
    "        # 计算多步损失函数\n",
    "        for t in range(0, targ.shape[1] - 1):\n",
    "            decoding_input = tf.expand_dims(targ[:, t], 1)\n",
    "            predictions, decoding_hidden, _ = decoder(\n",
    "                decoding_input, decoding_hidden, encoding_outputs)\n",
    "            loss += loss_function(targ[:, t+1], predictions)\n",
    "            \n",
    "    batch_loss = loss / int(targ.shape[0])\n",
    "    variables = encoder.trainable_variables + decoder.trainable_variables\n",
    "    gradients = tape.gradient(loss, variables)\n",
    "    optimizer.apply_gradients(zip(gradients, variables))\n",
    "    return batch_loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint_dir = os.path.join('seq2seq_checkpoints')\n",
    "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n",
    "checkpoint = tf.train.Checkpoint(optimizer=optimizer,\n",
    "                                 encoder=encoder,\n",
    "                                 decoder=decoder)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1 Batch 0 Loss 0.7767\n",
      "Epoch 1 Batch 100 Loss 0.3567\n",
      "Epoch 1 Batch 200 Loss 0.3398\n",
      "Epoch 1 Batch 300 Loss 0.2930\n",
      "Epoch 1 Batch 400 Loss 0.2768\n",
      "Epoch 1 Loss 0.3322\n",
      "Time take for 1 epoch 235.82640504837036 sec\n",
      "\n",
      "Epoch 2 Batch 0 Loss 0.2580\n",
      "Epoch 2 Batch 100 Loss 0.2535\n",
      "Epoch 2 Batch 200 Loss 0.2428\n",
      "Epoch 2 Batch 300 Loss 0.2180\n",
      "Epoch 2 Batch 400 Loss 0.1898\n",
      "Epoch 2 Loss 0.2214\n",
      "Time take for 1 epoch 218.2403221130371 sec\n",
      "\n",
      "Epoch 3 Batch 0 Loss 0.1776\n",
      "Epoch 3 Batch 100 Loss 0.1736\n",
      "Epoch 3 Batch 200 Loss 0.1663\n",
      "Epoch 3 Batch 300 Loss 0.1392\n",
      "Epoch 3 Batch 400 Loss 0.1116\n",
      "Epoch 3 Loss 0.1452\n",
      "Time take for 1 epoch 216.85228729248047 sec\n",
      "\n",
      "Epoch 4 Batch 0 Loss 0.1171\n",
      "Epoch 4 Batch 100 Loss 0.1086\n",
      "Epoch 4 Batch 200 Loss 0.1004\n",
      "Epoch 4 Batch 300 Loss 0.0907\n",
      "Epoch 4 Batch 400 Loss 0.0655\n",
      "Epoch 4 Loss 0.0917\n",
      "Time take for 1 epoch 216.78517651557922 sec\n",
      "\n",
      "Epoch 5 Batch 0 Loss 0.0665\n",
      "Epoch 5 Batch 100 Loss 0.0633\n",
      "Epoch 5 Batch 200 Loss 0.0618\n",
      "Epoch 5 Batch 300 Loss 0.0756\n",
      "Epoch 5 Batch 400 Loss 0.0370\n",
      "Epoch 5 Loss 0.0589\n",
      "Time take for 1 epoch 220.52611327171326 sec\n",
      "\n",
      "Epoch 6 Batch 0 Loss 0.0408\n",
      "Epoch 6 Batch 100 Loss 0.0391\n",
      "Epoch 6 Batch 200 Loss 0.0353\n",
      "Epoch 6 Batch 300 Loss 0.0515\n",
      "Epoch 6 Batch 400 Loss 0.0320\n",
      "Epoch 6 Loss 0.0391\n",
      "Time take for 1 epoch 216.77072072029114 sec\n",
      "\n",
      "Epoch 7 Batch 0 Loss 0.0267\n",
      "Epoch 7 Batch 100 Loss 0.0309\n",
      "Epoch 7 Batch 200 Loss 0.0357\n",
      "Epoch 7 Batch 300 Loss 0.0336\n",
      "Epoch 7 Batch 400 Loss 0.0158\n",
      "Epoch 7 Loss 0.0273\n",
      "Time take for 1 epoch 216.79898810386658 sec\n",
      "\n",
      "Epoch 8 Batch 0 Loss 0.0176\n",
      "Epoch 8 Batch 100 Loss 0.0178\n",
      "Epoch 8 Batch 200 Loss 0.0202\n",
      "Epoch 8 Batch 300 Loss 0.0221\n",
      "Epoch 8 Batch 400 Loss 0.0161\n",
      "Epoch 8 Loss 0.0202\n",
      "Time take for 1 epoch 226.10663747787476 sec\n",
      "\n",
      "Epoch 9 Batch 0 Loss 0.0086\n",
      "Epoch 9 Batch 100 Loss 0.0137\n",
      "Epoch 9 Batch 200 Loss 0.0219\n",
      "Epoch 9 Batch 300 Loss 0.0149\n",
      "Epoch 9 Batch 400 Loss 0.0132\n",
      "Epoch 9 Loss 0.0164\n",
      "Time take for 1 epoch 226.29279255867004 sec\n",
      "\n",
      "Epoch 10 Batch 0 Loss 0.0124\n",
      "Epoch 10 Batch 100 Loss 0.0144\n",
      "Epoch 10 Batch 200 Loss 0.0108\n",
      "Epoch 10 Batch 300 Loss 0.0207\n",
      "Epoch 10 Batch 400 Loss 0.0075\n",
      "Epoch 10 Loss 0.0140\n",
      "Time take for 1 epoch 240.50028800964355 sec\n",
      "\n"
     ]
    }
   ],
   "source": [
    "epochs = 10\n",
    "steps_per_epoch = len(input_tensor) // batch_size\n",
    "\n",
    "# 训练\n",
    "for epoch in range(epochs):\n",
    "    start = time.time()\n",
    "    encoding_hidden = encoder.initialize_hidden_state()\n",
    "    total_loss = 0\n",
    "    \n",
    "    for (batch, (inp, targ)) in enumerate(train_dataset.take(steps_per_epoch)):\n",
    "        batch_loss = train_step(inp, targ, encoding_hidden)\n",
    "        total_loss += batch_loss\n",
    "        # 计算总loss，打印日志\n",
    "        if batch % 100 == 0:\n",
    "            print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1, batch, batch_loss.numpy()))\n",
    "    \n",
    "    # 每5个周期（epoch）,保存（检查点）一次模型\n",
    "    if (epoch + 1) % 5 == 0:\n",
    "        checkpoint.save(file_prefix = checkpoint_prefix)\n",
    "    print('Epoch {} Loss {:.4f}'.format(epoch + 1, total_loss / steps_per_epoch))\n",
    "    print('Time take for 1 epoch {} sec\\n'.format(time.time() - start))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 验证\n",
    "### 给句子，返回翻译结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "def evaluate(input_sentence):\n",
    "    # 定义attention矩阵\n",
    "    attention_matrix = np.zeros((max_length_output, max_length_input))\n",
    "    # 预处理\n",
    "    input_sentence = preprocess_sentence(input_sentence)\n",
    "    # text到id转化\n",
    "    inputs = [input_tokenizer.word_index[token] for token in input_sentence.split(' ')]\n",
    "    # padding\n",
    "    inputs = keras.preprocessing.sequence.pad_sequences(\n",
    "        [inputs], maxlen = max_length_input, padding = 'post')\n",
    "    # 转化为tensor\n",
    "    inputs = tf.convert_to_tensor(inputs)\n",
    "    \n",
    "    results = ''\n",
    "    # encoding_hidden = encoder.initialize_hidden_state()\n",
    "    encoding_hidden = tf.zeros((1, units))\n",
    "    encoding_outputs, encoding_hidden = encoder(inputs, encoding_hidden)\n",
    "    decoding_hidden = encoding_hidden\n",
    "    \n",
    "    # eg:<start> -> A -> B -> ...\n",
    "    # decoding_input.shape: (1, 1)\n",
    "    decoding_input = tf.expand_dims([output_tokenizer.word_index['<start>']], 0)\n",
    "    for t in range(max_length_output):\n",
    "        predictions, decoding_hidden, attention_weights = decoder(\n",
    "                decoding_input, decoding_hidden, encoding_outputs)\n",
    "        \n",
    "        # attention_weights.shape: (batch_size, length, 1) (1, 16, 1)\n",
    "        # 缩维：(16)\n",
    "        attention_weights = tf.reshape(attention_weights, (-1,))\n",
    "        attention_matrix[t] = attention_weights.numpy()\n",
    "        \n",
    "        # predictions.shape: (batch_size, vocab_size) (1, 4935)\n",
    "        predicted_id = tf.argmax(predictions[0]).numpy()\n",
    "        \n",
    "        # 保存翻译结果\n",
    "        results += output_tokenizer.index_word[predicted_id] + ' '\n",
    "        \n",
    "        if output_tokenizer.index_word[predicted_id] == '<end>':\n",
    "            return results, input_sentence, attention_matrix\n",
    "        \n",
    "        decoding_input = tf.expand_dims([predicted_id], 0)\n",
    "    return results, input_sentence, attention_matrix"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 可视化权重"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_attention(attention_matrix, input_sentence, predicted_sentence):\n",
    "    fig = plt.figure(figsize=(10, 10))\n",
    "    ax = fig.add_subplot(1, 1, 1)\n",
    "    # 展示矩阵不同颜色\n",
    "    ax.matshow(attention_matrix, cmap = 'viridis')\n",
    "    \n",
    "    # 设置x轴和y轴\n",
    "    font_dict = {'fontsize' : 14}\n",
    "    ax.set_xticklabels([''] + input_sentence, fontdict = font_dict, rotation = 90)\n",
    "    ax.set_yticklabels([''] + predicted_sentence, fontdict = font_dict)\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 翻译"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "def translate(input_sentence):\n",
    "    results, input_sentence, attention_matrix = evaluate(input_sentence)\n",
    "    print('Input: %s' % (input_sentence))\n",
    "    print('Predicted translation: %s' % (results))\n",
    "    \n",
    "    attention_matrix = attention_matrix[:len(results.split(' ')), :len(input_sentence.split(' '))]\n",
    "    plot_attention(attention_matrix, input_sentence.split(' '), results.split(' '))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "seq2seq_checkpoints\\ckpt-2\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x189d0bd6320>"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 恢复检查点目录 （checkpoint_dir） 中最新的检查点\n",
    "print(tf.train.latest_checkpoint(checkpoint_dir))\n",
    "# latest_path = os.path.join(tf.train.latest_checkpoint(checkpoint_dir))\n",
    "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_dir))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input: <start> hace mucho frio aqui . <end>\n",
      "Predicted translation: it s very cold out . <end> \n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x720 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "translate(u'Hace mucho frío aquí.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input: <start> esta es mi vida . <end>\n",
      "Predicted translation: this is my life . <end> \n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x720 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "translate(u'Esta es mi vida.')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input: <start> sigues en casa ? <end>\n",
      "Predicted translation: i still at home . <end> \n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x720 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "translate(u'sigues en casa ?')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
